import nedb from 'nedb'

const CONFIG_DB_FILE_NAME = 'config.db';
const BOOKMARK_DB_FILE_NAME = 'bookMark.db';
const DOWNLOADTASKCONF_DB_FILE_NAME = 'downloadTaskConf.db';
const DOWNLOADTASKS_DB_FILE_NAME = 'downloadtasks.db';

export var CONFIG_DB;
export var BOOKMARK_DB;
export var DOWNLOADTASKCONF_DB;
export var DOWNLOADTASKS_DB;

/**
 * 初始化，加载数据库
 * @param basePath
 */
export function loadDb(basePath){
    let configDbPath = basePath + '/' + CONFIG_DB_FILE_NAME;
    let bookMarkDbPath = basePath + '/' + BOOKMARK_DB_FILE_NAME;
    let downloadTaskConfDbPath = basePath + '/' + DOWNLOADTASKCONF_DB_FILE_NAME;
    let downloadtasksDbPath = basePath + '/' + DOWNLOADTASKS_DB_FILE_NAME;

    CONFIG_DB = new nedb({
        filename: configDbPath,
        autoload: true
    });

    BOOKMARK_DB = new nedb({
        filename: bookMarkDbPath,
        autoload: true
    });

    DOWNLOADTASKCONF_DB = new nedb({
        filename: downloadTaskConfDbPath,
        autoload: true
    });

    DOWNLOADTASKS_DB = new nedb({
        filename: downloadtasksDbPath,
        autoload: true
    });

    compactDatafile();
}

/**
 * 压缩数据库
 *
 * 压缩会花费一些时间(在普通机器上，5w条记录花费130ms处理，并不会耗费太久)。
 * 在压缩执行期间，其他操作将不能执行，所以大部分项目不需要使用它。
 *
 * 假设不受corruptAlertThreshold参数的限制，压缩将会把损坏的记录全部移除掉。
 *
 * 压缩会强制系统将数据写入磁盘，这就保证了服务崩溃不会引起数据的全部丢失。
 * 最坏的情况就是崩溃发生在两个压缩同步操作之间，会导致全部数据的丢失。
 */
export function compactDatafile(){
    if(!CONFIG_DB){
        CONFIG_DB.persistence.compactDatafile();
    }
    if(!BOOKMARK_DB){
        BOOKMARK_DB.persistence.compactDatafile();
    }
    if(!DOWNLOADTASKCONF_DB){
        DOWNLOADTASKCONF_DB.persistence.compactDatafile();
    }
    if(!DOWNLOADTASKS_DB){
        DOWNLOADTASKS_DB.persistence.compactDatafile();
    }
}

/**
 * 根据数据库名，获取数据库对象
 * @param dbName
 * @returns {*}
 */
function getDbByName(dbName){
    if(dbName == 'CONFIG_DB'){
        return CONFIG_DB;
    }
    if(dbName == 'BOOKMARK_DB'){
        return BOOKMARK_DB;
    }
    if(dbName == 'DOWNLOADTASKCONF_DB'){
        return DOWNLOADTASKCONF_DB;
    }
    if(dbName == 'DOWNLOADTASKS_DB'){
        return DOWNLOADTASKS_DB;
    }

    return undefined;
}

/**
 * 插入方法
 * @param dbName 数据库名
 * @param params 插入条件，可以是{}，也可以是[{},{}]
 * @param callBack 回调函数
 * @constructor
 */
export function DBInsert(dbName, params, callBack){
    var db = getDbByName(dbName);

    if(!db){
        console.log('error');
        callBack({error: 'No DB'});
    }

    db.insert(params, function (err, data) {
        if(err){
            callBack({error: 'Insert Error'});
        }else{
            callBack(data);
        }
    });
}

/**
 * 查询方法
 * @param dbName 数据库名
 *
 * @param params 查询条件
 *
 * object类型，查询条件。
 * 支持使用比较运算符($lt, $lte, $gt, $gte, $in, $nin, $ne),
 * 逻辑运算符($or, $and, $not, $where),
 * 正则表达式进行查询。
 *
 * @param callBack 回调函数
 * @constructor
 */
export function DBFind(dbName, params, callBack){
    var db = getDbByName(dbName);

    if(!db){
        console.log('error');
        callBack({error: 'No DB'});
    }

    db.find(params, function (err, data) {
        if(err){
            callBack({error: 'Find Error'});
        }else{
            callBack(data);
        }
    });
}

/**
 * 修改方法
 * @param dbName 数据库名
 * @param queryParams 匹配条件，与find和findOne中query参数的用法一致
 * @param updataParams 更新数据
 * 指定文档更改规则。
 * 该参数可以是一个新的文档，也可以是一套修饰符，两者不能同时使用。
 * 使用修饰符时，如果需要更改的字段不存在，将会自动创建。
 * 可用的修饰符有
 *      $set(改变字段值),
 *      $unset(删除某一字段),
 *      $inc(增加某一字段),
 *      $min/$max(改变字段值，传入值需要小于/大于当前值),
 * 还有一些用在数组上的修饰符，$push, $pop, $addTopSet, $pull, $each, $slice
 *
 * @param options 支持的操作参数，object类型。
 * muti(默认false)，是否允许修改多条文档；
 * upsert(默认为false)，如果query没有匹配到结果集，
 *      有两种情况需要考虑，一个是update是一个简单的对象(不包含任何修饰符)，
 *      另一种情况是带有修饰符，对第一种情况会直接将该文档插入，
 *      对第二种情况会将通过修饰符更改后的文档插入；
 *
 * @param callBack 回调函数
 * @constructor
 */
export function DBUpdate(dbName, queryParams, updataParams, options, callBack){
    var db = getDbByName(dbName);

    if(!db){
        console.log('error');
        callBack({error: 'No DB'});
    }

    db.update(queryParams, updataParams, options, function (err, data) {
        if(err){
            callBack({error: 'Update Error'});
        }else{
            callBack(data);
        }
    });
}

/**
 * 删除方法
 * @param dbName 数据库名
 * @param queryParams 匹配条件，与find和findOne中query参数的用法一致
 * @param options 支持的操作参数，只有一个可用。muti(默认false)，允许删除多个文档。
 * @param callBack 回调函数
 * @constructor
 */
export function DBRemove(dbName, queryParams, options, callBack){
    var db = getDbByName(dbName);

    if(!db){
        console.log('error');
        callBack({error: 'No DB'});
    }

    db.remove(queryParams, options, function (err, data) {
        if(err){
            callBack({error: 'Remove Error'});
        }else{
            callBack(data);
        }
    });
}

/**
 * 增加索引
 * 作用：
 * NeDB支持索引。
 * 索引可以提高查询速度以及保证字段的唯一性。
 * 索引可以用在任何字段，包括嵌套很深的字段。
 * 目前，索引只能用来加速基本查询以及使用$in, $lt, $lte, $gt 和 $gte运算符的查询，
 * 如上find接口中示例所示。保证索引不为数组对象。
 * 方法可以在任何时候被调用，推荐在应用启动时就调用
 * (该方法是同步的,为1000个文档添加索引仅需35ms)。
 *
 * @param dbName 数据库名
 * @param options 支持的操作参数
 *
 * fieldName(必须): 索引字段，使用“.”给嵌套的字段加索引。
 * unique(可选，默认false): 字段唯一性约束。
 *      注意：唯一性约束会增加为两个文档中没有定义的字段添加索引的错误。
 * sparse(可选，默认false): 不能为没有定义的字段加索引。
 *      如果接受给多个文档中没有定义的字段添加索引，给需要该配置参数与unique一起使用。
 * expireAfterSeconds(可选，秒数): TTL索引，设置自动过期时间。
 *
 * @param callBack
 * @constructor
 */
export function DBEnsureIndex(dbName, options, callBack){
    var db = getDbByName(dbName);

    if(!db){
        console.log('error');
        callBack({error: 'No DB'});
    }

    db.ensureIndex(options, function (err, data) {
        if(err){
            callBack({error: 'Ensure Index Error'});
        }else{
            callBack(data);
        }
    });
}

/**
 * 删除索引
 * @param dbName 数据库名
 * @param filedName 索引字段名
 * @param callBack
 * @constructor
 */
export function DBRemoveIndex(dbName, filedName, callBack){
    var db = getDbByName(dbName);

    if(!db){
        console.log('error');
        callBack({error: 'No DB'});
    }

    db.removeIndex(filedName, function (err, data) {
        if(err){
            callBack({error: 'Remove Index Error'});
        }else{
            callBack(data);
        }
    });
}

/**
 * 计数
 * @param dbName 数据库名
 * @param params 匹配条件，与find和findOne中query参数的用法一致
 * @param callBack
 * @constructor
 */
export function DBCount(dbName, params, callBack){
    var db = getDbByName(dbName);

    if(!db){
        console.log('error');
        callBack({error: 'No DB'});
    }

    db.count(params, function (err, data) {
        if(err){
            callBack({error: 'Get Count Error'});
        }else{
            callBack(data);
        }
    });
}